/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief GIMnetAP (GIMnet AccessPoint) main program.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#define _GNU_SOURCE
#include <string.h>

#include "owndebug.h"
#include "ownutils.h"
#include "connectionDisp.h"
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <assert.h>
#include "GIMnetAP_version.h"
#include "servicelauncher.h"
#include "apsecurity.h"

////////////////////////////////////////////////////////////
/////////// Protocol Includes
////////////////////////////////////////////////////////////
#include "fastProtocol.h"
#include "hubctrl.h"

////////////////////////////////////////////////////////////
/////////// Protocol structures are currently stored here
////////////////////////////////////////////////////////////
static TFastProtocol fastProtocol;
static THubCtrl hubCtrl;
static TConnectionDisp connectionDisp;
static TConnectionList connectionList;
static TAPSecurityAuthentication credentials = { "clientnone", 
                                                 10, 
                                                 "apsnone", 
                                                 7, 
                                                 "apnone", 
                                                 6 };
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

static volatile int run = 1;
static pid_t service_pids[32]; ///Service Pids
static int service_pids_count = 0;
static int tcp_port = 0; // Port to use
static int udp_port = 0;
static char socket_file[256] = ""; // Enough
static const char *authenticationFile = NULL;

//*****************************************************************************
//*****************************************************************************

int signal_handler_thread(void *ptr)
{
  dPrint(ODTEST, "Signal handler thread (%ld) starting...", pthread_self());

  while (run) {
    // Now, wait for requested signals to arrive
    int sig;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGPIPE);
    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGALRM);
    sigwait(&mask, &sig);

    // What to do?
    switch(sig) {
    case SIGALRM:
      //dPrint(ODTEST, "SIGALRM received, calling Timer Event Handler for ConnectionDisp");
      connectionDispProcessTimerEvent(&connectionDisp);
      break;

    case SIGINT:
    case SIGTERM:
      run = 0;
      // Got signal, sigwait returned!
      dPrint(ODSTATUS,"GIMnetAP - Received signal '%d' (%s)", sig, strsignal(sig));
      dPrint(ODSTATUS,"Terminating Connection module...");
      connectionDispTerminate(&connectionDisp);
      dPrint(ODSTATUS,"Done.");
      break;

    case SIGPIPE:
      dPrint(ODINFO,"SIGPIPE received - ignoring.");
      break;

    default:
      dPrint(ODWARN,"Unexpected signal received, Ignoring! (%d)", sig);
      break;
    }
    
    if (run == 0) {
      dPrint(ODSTATUS,"Terminating GIMnetAP - Please wait until all threads have exited.");
    }
  }
  return 0;
}
//*****************************************************************************

void showUsage()
{
  // Usage instructions
  fprintf(stderr,"\n");
  fprintf(stderr,"Usage:\tGIMnetAP [OPTIONS]\n");
  fprintf(stderr,"  <-p tcp_port>\t\t\tSet GIMnetAP to listen in TCP port 'tcp_port'\n"); 
  fprintf(stderr,"  [-u udp_port]\t\t\tSet GIMnetAP to listen in UDP port 'udp_port'\n");
  fprintf(stderr,"  [-s socket_file]\t\tPath to UNIX socket file\n");
  fprintf(stderr,"  [-c host:port[:APID]]\t\tCreate AP2AP connection to host:port\n"); 
  fprintf(stderr,"  [-i localhubid]\t\tSpecify the ID of this AP\n");
  fprintf(stderr,"  [-r APID:r_begin[:r_end]]\tTraffic to r_begin-r_end is redirected to AP 'APID':\n");
  fprintf(stderr,"  [-d default_route]\t\tUnknown traffic always routed here\n");
  fprintf(stderr,"  [-D delay]\t\t\tApply 'delay' (in ms) for next specified AP2AP link\n");
  fprintf(stderr,"  [-A authfile]\t\t\tRequire authentication, use keyfile 'authfile'\n");
  fprintf(stderr,"  [-S servicename]\t\tLaunch given service together with this GIMnetAP.\n");
  fprintf(stderr,"  [-v]\t\t\t\tIncrease verbosity by 1\n");
  fprintf(stderr,"  [-V verb_lvl]\t\t\tSet verbosity level to 'verb_level'\n");
  fprintf(stderr,"  [-q]\t\t\t\tQuiet mode\n");
  fprintf(stderr,"\t\t\n");
  fprintf(stderr, "\n");
}
//*****************************************************************************

void registerProtocols()
{
  ///////////////////////////////////////////////////
  ///// THIS REGISTERS ALL PROTOCOLS AVAILABLE! /////
  ///////////////////////////////////////////////////

  //// FastProtocol section ////
  fastProtocolInit(&fastProtocol);
  protocolRegisterProtocol(FASTPROTOCOL_NUMBER, 
                           fastProtocolGetProtocolEntry(&fastProtocol));
  //////////////////////////////


  //// HubCtrl section ////
  hubctrlInit(&hubCtrl);
  protocolRegisterProtocol(HUBCTRLPROTOCOL_NUMBER,
                           hubctrlGetProtocolEntry(&hubCtrl));
  /////////////////////////

  //// <SomeOtherProtocol> section ////
  // someotherInit(&someOther);
  // protocolRegisterProtocol(someotherPROTOCOL_NUMBER,
  //                          someotherGetProtocolEntry(&someOther));
  /////////////////////////////////////

}
//*****************************************************************************

void deinitServices()
{
  printf("Deinitializing %d Services...\n", service_pids_count);
         
  int i;
  for(i=0; i < service_pids_count; ++i) {
    servicelauncherSignalService(service_pids[i], SIGTERM);
  }
  for(i=0; i < service_pids_count; ++i) {
    servicelauncherWaitService(service_pids[i]);
  }
  service_pids_count = 0;

  printf("Deinitializing Services completed.\n");
}
//*****************************************************************************

int initServices(const char **services, const int servicecount)
{
  int result = 0;
  int i;
  char *param[10];
  char portstr[32];

  // Setups
  memset(param, 0x00, sizeof(param));
  memset(service_pids, 0x00, sizeof(service_pids));
  sprintf(portstr,"%d", tcp_port);

  // Init parameters for the call.
  int pi = 0;
  param[pi++] = NULL;
  param[pi++] = "-u";
  param[pi++] = "127.0.0.1";
  param[pi++] = "-p";
  param[pi++] = portstr;
 
  // If authenticationFile is set, pass it to services as well.
  if (authenticationFile) {
    param[pi++] = "-A";
    param[pi++] = (char *)authenticationFile;
  }

  param[pi++] = NULL;

  for(i=0; i < servicecount; ++i) {
    char *theservice = (char*)services[i];
    param[0] = theservice;
    printf("Initializing Service '%s'...\n", services[i]);

    pid_t newpid = servicelauncherLaunchService(theservice, param);

    // Give it a few milliseconds to stabilize
    ownSleep_ms(200);

    if (newpid > 0 &&
        servicelauncherGetServiceStatus(newpid) == 0) {
      printf("Succesfully executed service '%s'\n", theservice);
      service_pids[service_pids_count++] = newpid;
      
    } else {
      printf("Failed to execute service '%s'! Fatal error.\n", theservice);
      result = -1;
    }
  }

  return result;
}
//*****************************************************************************

int initProtocols()
{
  // Set key information
  hubctrlSetAP2APSecret(&hubCtrl, credentials.secretAP2AP, credentials.secretAP2APSize);
  hubctrlSetAPServiceSecret(&hubCtrl, credentials.secretAPService, credentials.secretAPServiceSize);
  hubctrlSetClientSecret(&hubCtrl, credentials.secretAPClient, credentials.secretAPClientSize);

  return 0;
}
//*****************************************************************************
//*****************************************************************************

int main(int argc, char *argv[]) 
{
  int h2h_delay = 0; // Default AP2AP delay
  int i,r = 1;
  int verbose = 1;
  FILE *output = stderr;
  int paramParseLoop = 1;
  const char *service[32]; // Won't need more than 32 ;)
  unsigned int servicecount = 0;

  // Set up utils (debugs mostly)
  debugInit();
  debugSetGlobalDebugLvl(2);
  debugRemoveMode( DEBUG_MODE_TIMESTAMP_HH_MM_SS |  DEBUG_MODE_TIMESTAMP_HH_MM_SS_MS );
  debugAddMode( DEBUG_MODE_TIMESTAMP_DELTA_SS_US );


  // Now; Set to BLOCK ALL SIGNALS
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGTERM);
  sigaddset(&mask, SIGPIPE);
  sigaddset(&mask, SIGHUP);
  sigaddset(&mask, SIGALRM);
  pthread_sigmask(SIG_BLOCK, &mask, NULL);

  // Start signal handler thread
  ownThreadHandle sigthread = ownThread_Create((void*)signal_handler_thread, NULL);



  // First of all; init generic protocol handler
  protocolInit(); // Init generic protocol handler

  // Register protocols
  registerProtocols();
  
  // Init ConnectionList module.
  if (connectionListInit(&connectionList) != 0) {
    printf("Initializing ConnectionList module failed...\n\"n");
    exit(1);
  }

  // Init routing module.
  if (routingInit(0x00000000) != 0) {
    printf("Initializing Routing module failed...\n\"n");
    exit(1);

  }

  // Initialize connection dispatcher (no listeners yet, only datainit)
  if (connectionDispInit(&connectionDisp, &connectionList) != 0) {
    printf("Initializing ConnectionDisp module failed...\n\n");
    exit(1);
  }


  // Print version
  printf("\nGIMnetAP %s\n\n", GIMNETAP_VERSIONSTRING);


  // Decode command line parameters.
  while(paramParseLoop) {
    int c;
    c = getopt(argc, argv, "f:S:A:p:hqvV:c:r:d:i:u:s:D:");
    switch(c) {
      
    case 'v':
      // Increase only if not previously set to 0 (by 'quiet')
      if (verbose > 0) verbose++;
      debugSetGlobalDebugLvl(verbose);
      break;
    
    case 'V':
      verbose = atoi(optarg);
      debugSetGlobalDebugLvl(verbose);
      break;

    case 'q':
      verbose = 0;
      debugSetLogFilename(NULL);
      break;
    
    case 'A': {
      authenticationFile = optarg;
      printf("Using AuthenticationFile: '%s'\n", optarg);
      if (apsecurityReadAuthenticationFile(&credentials,
                                           authenticationFile)) {
        printf("Failed to read given Authentication file '%s'! Fatal error, abort!\n", 
               authenticationFile);
        exit(1);
      }
      break;
    }

    case 'l': 
      output = fopen(optarg,"wt");
      if (output == NULL) {
        printf("Unable to open log file '%s'!\r\n", optarg);
        exit(2);
      }
      printf("Enabled: Outputting debug info to '%s'\r\n", optarg);
      debugSetLogFILE(output);
      break;
      
      //////////////////////////////////
      ////      BASIC SETTINGS;     ////
      //////////////////////////////////
    case 'p':
      // Port to use
      if (sscanf(optarg,"%d", &tcp_port) != 1) {
        printf("Invalid TCP port specified.\r\n");
        exit(1);

      } else {
        connectionDispAddServerTCP(&connectionDisp, tcp_port,NULL);

      }
      break;

    case 'u':
      // Port to use
      if (sscanf(optarg,"%d", &udp_port) != 1) {
        printf("Invalid UDP port specified.\r\n");
        exit(1);
      }
      break;

    case 's':
      strncpy(socket_file, optarg, sizeof(socket_file));
      socket_file[sizeof(socket_file)-1] = 0; // Force terminate
      break;

    case 'c': {
      const char *host, *port;
      int porti; 
      
      host = strtok(optarg, ":");
      port = strtok(NULL, ":");
      if (host == NULL || 
          port == NULL || 
          sscanf(port, "%d", &porti) != 1) {
        printf("Invalid AP2AP entry! Entry must be in format <host>:<port>!\r\n");
        exit(1);
      }

      // Add entry.
      printf("Adding entry: %s : %d\n", host, porti);
      connectionDispAddClientTCP(&connectionDisp, host, porti);

      break;
    }
      
    case 'r': {
      int i,r_begin, r_end, gateway;
      const char *r_begin_str, *r_end_str, *gateway_str;
      
      // Split the string
      gateway_str = strtok(optarg, ":");
      r_begin_str = strtok(NULL, ":");
      r_end_str   = strtok(NULL, ":"); // Optional
      
      if (r_begin_str == NULL || gateway_str == NULL) {
        printf("Invalid route definition! "
               "Entry must be in format: <hubID>:<route_begin>[:route_end]\r\n");
        exit(1);
      }

      r = sscanf(gateway_str,"%x",&gateway);
      r *= sscanf(r_begin_str,"%x",&r_begin);
      if (r_end_str) {
        r *= sscanf(r_end_str,  "%x",&r_end);
      } else {
        r_end = r_begin;
      }
      
      // Check that the sscanfs: succeeded
      if (r != 1) {
        printf("Invalid route definition! Values must be HEX numbers.\r\n");
        exit(1);
      }

      printf("Adding route for '0x%04x-0x%04x' over gateway '0x%04x'\r\n", 
             r_begin, r_end, gateway);
      
      // For all addresses between begin-end. (Inclusive)
      for(i=r_begin; i <= r_end; i++) {
        routingSetRoute(i, gateway);
      }
      break; }

    case 'd': {
      unsigned int droute;
      const int r = sscanf(optarg,"%x",&droute); // Only take 16 lower bits.
      if (r == 1) {
        printf("Setting DEFAULT route to 0x%08x\n", droute);
        routingSetDefaultGW(droute);
        
      } else {
        dPrint(ODERROR,"Invalid default route; must be in format 0x00000000");
        exit(1);

      }
      break; 
    }

    case 'i': {
      int localID;
      r = sscanf(optarg,"%d", &localID);
      assert(r == 1);

      printf("Setting local hubID to 0x%04x\n", localID);
      connectionListSetLocalHubID(&connectionList, localID<<16);
      break; 
    }

    case 'D': {
      // AP2AP delay
      r = sscanf(optarg,"%d", &h2h_delay);
      if (r != 1) {
        printf("ERROR in parameters: '%s' is not a valid AP2AP-delay!\n", 
               optarg);
        exit(1);
      }
      break;
    }

    case 'S':
      printf("Adding service %u = '%s'\n", servicecount, optarg);
      service[servicecount++] = optarg;
      break;
        
      //////////////////////////////////
      //// GENERIC:                 ////
      //////////////////////////////////
      
    case 'h':
    case '?':
      showUsage();
      exit(1);
      break;
      
    case -1:
      paramParseLoop = 0;
      break;
    }
  }

  // Run InitProtocols after reading parameters
  initProtocols();

  
  // Set the global debug level to 'verbose'
  debugSetGlobalDebugLvl(verbose);

  // Value legality checks ; Apply defaults.
  if (!tcp_port) {
    tcp_port = 40002;
    connectionDispAddServerTCP(&connectionDisp, tcp_port,NULL);
  }


  // Settings echoing
  printf("Starting GIMnetAP at port %d\n", tcp_port);
  if (verbose > 1) {
    printf("Verbosity level set to %d\n", verbose);
  }
  printf("\n");

  // Initialize socket
  if (connectionDispInitializeSockets(&connectionDisp) == 0) {

    // Now; Initialize Services.
    if (initServices(service, servicecount) == 0) {
         
      // Execute!
      connectionDispExecute(&connectionDisp);
      
      // Deinitialize all submodules.
      connectionDispTerminate(&connectionDisp);
      connectionDispDeinit(&connectionDisp);
      connectionListDeinit(&connectionList);
    }

  } else {
    printf("Failed to Initialize Sockets. Fatal error. Exit.\n");

  }

  // Always deinit.
  routingDeinit();
  deinitServices();
 

  // If the socket file was set, attempt to remove it on exit.
  if (socket_file[0]) unlink(socket_file);
  
  run = 0;
  
  int retval = 0;
  printf("Waiting for signal handler to terminate...\n");
  pthread_kill(*sigthread, SIGTERM);
  ownThread_Join(sigthread, &retval);
  printf("GIMnetAP exiting.\r\n");

  return r;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
  
